package cn.jingyuan.bee.utils.bean;

import cn.jingyuan.bee.utils.*;
import cn.jingyuan.bee.utils.lang.Assert;
import cn.jingyuan.bee.utils.map.CaseInsensitiveMap;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Bean 信息描述做为 BeanInfo 替代方案，此对象持有 JavaBean 中的 setters 和 getters 等相关信息描述。<br>
 * 查找 Getter 和 Setter 方法时会：
 * <pre>
 *  1. 忽略字段和方法名的大小写；
 *  2. Getter 查找 getXXX、isXXX、getIsXXX；
 *  3. Setter 查找 setXXX、setIsXXX；
 *  4. Setter 忽略参数值与字段值不匹配的情况，因此有多个参数类型的重载时，会调用首次匹配的；
 * </pre>
 */
public class BeanDesc implements Serializable {

    /** Bean 类 */
    private final Class<?> beanClass;

    /** 属性 Map */
    private final Map<String, PropDesc> propMap = new LinkedHashMap<>();

    /**
     * 构造
     *
     * @param beanClass Bean 类
     */
    public BeanDesc(Class<?> beanClass) {
        Assert.notNull(beanClass);
        this.beanClass = beanClass;
        init();
    }

    /**
     * 获取 Bean 的全类名
     *
     * @return Bean 的类名
     */
    public String getName() {
        return this.beanClass.getName();
    }

    /**
     * 获取 Bean 的简单类名
     *
     * @return Bean 的类名
     */
    public String getSimpleName() {
        return this.beanClass.getSimpleName();
    }

    /**
     * 获取字段名-字段属性 Map
     *
     * @param ignoreCase 是否忽略大小写，true 为忽略，false 不忽略
     *
     * @return 字段名-字段属性 Map
     */
    public Map<String, PropDesc> getPropMap(boolean ignoreCase) {
        return ignoreCase ? new CaseInsensitiveMap<>(1, this.propMap) : this.propMap;
    }

    /**
     * 获取字段属性列表
     *
     * @return {@link PropDesc} 列表
     */
    public Collection<PropDesc> getProps() {
        return this.propMap.values();
    }

    /**
     * 获取属性，如果不存在返回 null
     *
     * @param fieldName 字段名
     *
     * @return {@link PropDesc}
     */
    public PropDesc getProp(String fieldName) {
        return this.propMap.get(fieldName);
    }

    /**
     * 获得字段名对应的字段对象，如果不存在返回 null
     *
     * @param fieldName 字段名
     *
     * @return 字段值
     */
    public Field getField(String fieldName) {
        final PropDesc desc = this.propMap.get(fieldName);
        return null == desc ? null : desc.getField();
    }

    /**
     * 获取 Getter 方法，如果不存在返回 null
     *
     * @param fieldName 字段名
     *
     * @return Getter 方法
     */
    public Method getGetter(String fieldName) {
        final PropDesc desc = this.propMap.get(fieldName);
        return null == desc ? null : desc.getGetter();
    }

    /**
     * 获取 Setter 方法，如果不存在返回 null
     *
     * @param fieldName 字段名
     *
     * @return Setter 方法
     */
    public Method getSetter(String fieldName) {
        final PropDesc desc = this.propMap.get(fieldName);
        return null == desc ? null : desc.getSetter();
    }

    /**
     * 初始化<br>
     * 只有与属性关联的相关 Getter 和 Setter 方法才会被读取，无关的 getXXX 和 setXXX 都被忽略
     *
     * @return this
     */
    private BeanDesc init() {
        for (Field field : ReflectUtils.getFields(this.beanClass)) {
            if (!ModifierUtils.isStatic(field)) {
                // 只针对非 static 属性
                this.propMap.put(field.getName(), createProp(field));
            }
        }
        return this;
    }

    /**
     * 根据字段创建属性描述<br>
     * 查找 Getter 和 Setter 方法时会：
     * <pre>
     * 1. 忽略字段和方法名的大小写
     * 2. Getter 查找 getXXX、isXXX、getIsXXX
     * 3. Setter 查找 setXXX、setIsXXX
     * 4. Setter 忽略参数值与字段值不匹配的情况，因此有多个参数类型的重载时，会调用首次匹配的
     * </pre>
     *
     * @param field 字段
     *
     * @return {@link PropDesc}
     */
    private PropDesc createProp(Field field) {
        final String fieldName = field.getName();
        final Class<?> fieldType = field.getType();
        final boolean isBooeanField = BooleanUtils.isBoolean(fieldType);

        Method getter = null;
        Method setter = null;

        String methodName;
        Class<?>[] parameterTypes;
        for (Method method : ReflectUtils.getMethods(this.beanClass)) {
            parameterTypes = method.getParameterTypes();
            if (parameterTypes.length > 1) {
                // 多于 1 个参数说明非 Getter 或 Setter
                continue;
            }

            methodName = method.getName();
            if (parameterTypes.length == 0) {
                // 无参数，可能为 Getter 方法
                if (isMatchGetter(methodName, fieldName, isBooeanField)) {
                    // 方法名与字段名匹配，则为 Getter 方法
                    getter = method;
                }
            } else if (isMatchSetter(methodName, fieldName, isBooeanField)) {
                // 只有一个参数的情况下方法名与字段名对应匹配，则为 Setter 方法
                setter = method;
            }
            if (null != getter && null != setter) {
                // 如果 Getter 和 Setter 方法都找到了，不再继续寻找
                break;
            }
        }
        return new PropDesc(field, getter, setter);
    }

    /**
     * 方法是否为 Getter 方法<br>
     * 匹配规则如下（忽略大小写）：
     * <pre>
     * 字段名    -》 方法名
     * isName  -》 isName
     * isName  -》 isIsName
     * isName  -》 getIsName
     * name     -》 isName
     * name     -》 getName
     * </pre>
     *
     * @param methodName 方法名
     * @param fieldName 字段名
     * @param isBooleanField 是否为 Boolean 类型字段
     *
     * @return 是否匹配
     */
    private boolean isMatchGetter(String methodName, String fieldName, boolean isBooleanField) {
        // 全部转为小写，忽略大小写比较
        methodName = methodName.toLowerCase();
        fieldName = fieldName.toLowerCase();

        if (!methodName.startsWith("get") && !methodName.startsWith("is")) {
            // 非标准 Getter 方法
            return false;
        }
        if ("getClass".equals(methodName)) {
            // 跳过 getClass 方法
            return false;
        }

        // 针对 Boolean 类型特殊检查
        if (isBooleanField) {
            if (fieldName.startsWith("is")) {
                // 字段已经是 is 开头
                if (methodName.equals(fieldName) // isName -》 isName
                        || methodName.equals("get" + fieldName) // isName -》 getIsName
                        || methodName.equals("is" + fieldName) // isName -》 isIsName
                ) {
                    return true;
                }
            } else if (methodName.equals("is" + fieldName)) {
                // 字段非 is 开头， name -》 isName
                return true;
            }
        }

        // 包括 boolean 的任何类型只有一种匹配情况：name -》 getName
        return methodName.equals("get" + fieldName);
    }

    /**
     * 方法是否为 Setter 方法<br>
     * 匹配规则如下（忽略大小写）：
     *
     * <pre>
     * 字段名    -》 方法名
     * isName  -》 setName
     * isName  -》 setIsName
     * name     -》 setName
     * </pre>
     *
     * @param methodName 方法名
     * @param fieldName 字段名
     * @param isBooleanField 是否为 Boolean 类型字段
     *
     * @return 是否匹配
     */
    private boolean isMatchSetter(String methodName, String fieldName, boolean isBooleanField) {
        // 全部转为小写，忽略大小写比较
        methodName = methodName.toLowerCase();
        fieldName = fieldName.toLowerCase();

        // 非标准 Setter 方法跳过
        if (!methodName.startsWith("set")) {
            return false;
        }

        // 针对 Boolean 类型特殊检查
        if (isBooleanField && fieldName.startsWith("is")) {
            // 字段是 is 开头
            if (methodName.equals("set" + StringUtils.removePrefix(fieldName, "is"))// isName -》 setName
                    || methodName.equals("set" + fieldName)// isName -》 setIsName
            ) {
                return true;
            }
        }

        // 包括 boolean 的任何类型只有一种匹配情况：name -》 setName
        return methodName.equals("set" + fieldName);
    }

    /**
     * 属性描述
     */
    public static class PropDesc {

        /** 字段 */
        private final Field field;

        /** Getter 方法 */
        private final Method getter;

        /** Setter 方法 */
        private final Method setter;

        /**
         * 构造<br>
         * Getter 和 Setter 方法设置为默认可访问
         *
         * @param field 字段
         * @param getter get 方法
         * @param setter set 方法
         */
        public PropDesc(Field field, Method getter, Method setter) {
            this.field = field;
            this.getter = ClassUtils.setAccessible(getter);
            this.setter = ClassUtils.setAccessible(setter);
        }

        /**
         * 获取字段名
         *
         * @return 字段名
         */
        public String getFieldName() {
            return null == this.field ? null : this.field.getName();
        }

        /**
         * 获取字段
         *
         * @return 字段
         */
        public Field getField() {
            return this.field;
        }

        /**
         * 获得字段类型<br>
         * 先获取字段的类型，如果字段不存在，则获取 Getter 方法的返回类型，否则获取 Setter 的第一个参数类型
         *
         * @return 字段类型
         */
        public Type getFieldType() {
            if (null != this.field) {
                return TypeUtils.getType(this.field);
            }
            return findPropType(getter, setter);
        }

        /**
         * 获得字段类型<br>
         * 先获取字段的类型，如果字段不存在，则获取 Getter 方法的返回类型，否则获取 Setter 的第一个参数类型
         *
         * @return 字段类型
         */
        public Class<?> getFieldClass() {
            if (null != this.field) {
                return TypeUtils.getClass(this.field);
            }
            return findPropClass(getter, setter);
        }

        /**
         * 获取 Getter 方法，可能为 {@code null}
         *
         * @return Getter 方法
         */
        public Method getGetter() {
            return this.getter;
        }

        /**
         * 获取 Setter 方法，可能为 {@code null}
         *
         * @return {@link Method}Setter 方法对象
         */
        public Method getSetter() {
            return this.setter;
        }

        /**
         * 获取字段值<br>
         * 首先调用字段对应的 Getter 方法获取值，如果 Getter 方法不存在，则判断字段如果为 public，则直接获取字段值
         *
         * @param bean Bean 对象
         *
         * @return 字段值
         */
        public Object getValue(Object bean) {
            if (null != this.getter) {
                return ReflectUtils.invoke(bean, this.getter);
            } else if (ModifierUtils.isPublic(this.field)) {
                return ReflectUtils.getFieldValue(bean, this.field);
            }
            return null;
        }

        /**
         * 设置 Bean 的字段值<br>
         * 首先调用字段对应的 Setter 方法，如果 Setter 方法不存在，则判断字段如果为 public，则直接赋值字段值
         *
         * @param bean Bean 对象
         * @param value 值
         *
         * @return this
         */
        public PropDesc setValue(Object bean, Object value) {
            if (null != this.setter) {
                ReflectUtils.invoke(bean, this.setter, value);
            } else if (ModifierUtils.isPublic(this.field)) {
                ReflectUtils.setFieldValue(bean, this.field, value);
            }
            return this;
        }

        /**
         * 通过 Getter 和 Setter 方法中找到属性类型
         *
         * @param getter Getter 方法
         * @param setter Setter 方法
         *
         * @return {@link Type}
         */
        private Type findPropType(Method getter, Method setter) {
            Type type = null;
            if (null != getter) {
                type = TypeUtils.getReturnType(getter);
            }
            if (null == type && null != setter) {
                type = TypeUtils.getParamType(setter, 0);
            }
            return type;
        }

        /**
         * 通过 Getter 和 Setter 方法中找到属性类型
         *
         * @param getter Getter 方法
         * @param setter Setter 方法
         *
         * @return {@link Type}
         */
        private Class<?> findPropClass(Method getter, Method setter) {
            Class<?> type = null;
            if (null != getter) {
                type = TypeUtils.getReturnClass(getter);
            }
            if (null == type && null != setter) {
                type = TypeUtils.getFirstParamClass(setter);
            }
            return type;
        }

    }

}